#include <stdio.h>
#include <stdlib.h>
#include "baash.h"

bool parser(env_var_t *v){
	FILE * stream ; 
	bool ret ; 
	command_info_t cmd_info ; 
	char * line;
	char * aux;
	size_t largo;
	int i = 0 ;

	cmd_info.op = NONE ;
	cmd_info.buffer = (char**) malloc ( sizeof(char*) * BUFFER_SIZE ) ; 
	if ( cmd_info.buffer == NULL ) { 
		perror(strerror(errno)); 
		exit(EXIT_FAILURE) ; 
	}
	for ( i = 0 ; i < BUFFER_SIZE ; i++ ){ 
		cmd_info.buffer[i] = NULL ;
	}
	i = 0 ; 

	stream = stdin ;
	getline(&line,&largo,stream);
	aux = strtok(line, " \n");

	while(aux != NULL){
		(cmd_info.buffer)[i] = aux;
		if ( !strcmp(aux,"|") || !strcmp(aux,"<") || !strcmp(aux,">") ) {
			cmd_info.op = get_operator(aux[0]) ; 
			cmd_info.op_pos = i ; 
		}
		aux = strtok(NULL," \n");
		i++;
	}

	cmd_info.buffer_size = i ; 
	ret = parse_command(v,&cmd_info) ;
	return ret ;
	
}

operator_t get_operator (char c) { 
	switch (c) { 
		case '|' : return PIPE ; 
		case '<' : return INPUT ; 
		case '>' : return OUTPUT ; 
	}
	return NONE ;
}


void exec_command(command_info_t * cmd_info,env_var_t * v){
	int status ;
	bool concurrent_flag = FALSE ;
	int pid ;

	if ( !strcmp(cmd_info->buffer[cmd_info->buffer_size-1],"&") ) 
		concurrent_flag = TRUE ; 

	if ( cmd_info->op != NONE ) { 
		perform_according_action(cmd_info,v) ; 
		return ;
	}
		
	pid = fork();
	if ( pid == -1 ){
		perror(strerror(errno));
		exit(EXIT_FAILURE) ;
	} else if ( !pid ) { 
		execv(cmd_info->buffer[0], cmd_info->buffer);
	} else if ( pid > 0 && concurrent_flag == FALSE ) { 
		waitpid(pid,&status,0) ;
	}
	
	if ( concurrent_flag == TRUE ) { 
		printf("pid=%d",pid) ;
	}
	
}


void perform_according_action (command_info_t * cmd_info,env_var_t *v) {
	switch(cmd_info->op) { 
		case PIPE : exec_pipe(cmd_info, v) ; return ;
		case INPUT : exec_input(cmd_info) ; return ;
		case OUTPUT : exec_output(cmd_info) ; return ;
		case NONE : return ;
	}
}

void exec_pipe(command_info_t * cmd_info,env_var_t * v) { 
	int pid1, pid2, i, j, status ;
	char** argv_lvalue  ;
	char** argv_rvalue 	;
	argv_lvalue = (char**) malloc (sizeof(char*)*cmd_info->buffer_size) ; 
	argv_rvalue = (char**) malloc (sizeof(char*)*cmd_info->buffer_size) ; 
	
	for ( i = 0, j = 0 ; i<cmd_info->buffer_size ; i++ ) { 
		if ( i < cmd_info->op_pos ) {	
			argv_lvalue[i] = cmd_info->buffer[i] ; 
		} else if ( i>cmd_info->op_pos ) { 
			argv_rvalue[j] = cmd_info->buffer[i] ;  
			j++  ;
		}
	}

	if ( pipe(cmd_info->fds) ) { 
		perror(strerror(errno)) ; 
	} 

	pid1 = fork() ; 
	if ( pid1 ) pid2 = fork() ; 
	if ( !pid1  ) { 
		close(STDOUT_FILENO) ;
		close(cmd_info->fds[0]) ; 
		dup(cmd_info->fds[1]) ; 
		execv(cmd_info->buffer[0],argv_lvalue) ; 
		exit(EXIT_SUCCESS) ; 
	} 
	if ( !pid2  )  { 
		close(STDIN_FILENO) ; 
		close(cmd_info->fds[1]) ; 
		dup(cmd_info->fds[0]) ; 
		execv(search_cmd_dir_in_path(cmd_info->buffer[cmd_info->op_pos+1],v),argv_rvalue) ; 
		exit(EXIT_SUCCESS) ; 
	}
	if ( pid1 && pid2 ) { 
		close(cmd_info->fds[0]) ; 
		close(cmd_info->fds[1]) ; 
		waitpid(pid1,&status,0) ;
		waitpid(pid2,&status,0) ;
		return ;
	}
}
		
void exec_input (command_info_t* cmd_info) {
	int fid, pid, status ; 
	int flags, perm, i ; 
	char** argv_lvalue ; 
	flags = O_RDONLY ;
	perm = S_IWUSR|S_IRUSR ;
	fid = open(cmd_info->buffer[cmd_info->op_pos+1],flags,perm) ; 
	
	argv_lvalue = (char**) malloc (sizeof(char*)*BUFFER_SIZE) ; 
	if(argv_lvalue == NULL ) { perror("") ; } 

	for ( i = 0 ; i<cmd_info->buffer_size ; i++ ) { 
		if ( i < cmd_info->op_pos ) {	
			argv_lvalue[i] = cmd_info->buffer[i] ; 
		}
	}

	if ( fid<0 ) {perror("") ; }  
	pid = fork() ; 
	if ( !pid ) { 
		close(STDIN_FILENO) ; 
		dup(fid) ; 
		execv(argv_lvalue[0],argv_lvalue) ; 
	} else if (pid) { 
		waitpid(pid,&status,0) ; 
		close (fid) ;
		return ; 
	}

}
void exec_output (command_info_t* cmd_info) {
	int fid, pid, status ; 
	int flags, perm, i ; 
	char** argv_lvalue ; 
	flags = O_WRONLY|O_CREAT|O_TRUNC ; 
	perm = S_IWUSR|S_IRUSR ;
	fid = open(cmd_info->buffer[cmd_info->op_pos+1],flags,perm) ; 
	
	argv_lvalue = (char**) malloc (sizeof(char*)*BUFFER_SIZE) ; 
	if(argv_lvalue == NULL ) { perror("") ; } 

	for ( i = 0 ; i<cmd_info->buffer_size ; i++ ) { 
		if ( i < cmd_info->op_pos ) {	
			argv_lvalue[i] = cmd_info->buffer[i] ; 
		}
	}

	if ( fid<0 ) {perror("") ; }  
	pid = fork() ; 
	if ( !pid ) { 
		close(STDOUT_FILENO) ; 
		dup(fid) ; 
		execv(argv_lvalue[0],argv_lvalue) ; 
	} else if (pid) { 
		waitpid(pid,&status,0) ; 
		close (fid) ;
		return ; 
	}

}


bool parse_command(env_var_t * v, command_info_t * cmd_info){ 
	if(cmd_info->buffer[0] == NULL ) {
		return TRUE ; 
	}else if(!strcmp(cmd_info->buffer[0],"exit")){
		return FALSE;
	}else if(!strcmp(cmd_info->buffer[0], "cd")){
		change_dir(cmd_info->buffer[1],v);
	} else if ( strrchr(cmd_info->buffer[0],'.') == NULL && 
				strrchr(cmd_info->buffer[0],'/') == NULL 	){
					cmd_info->buffer[0]=search_cmd_dir_in_path(cmd_info->buffer[0],v) ;
					if (cmd_info->buffer[0] == NULL ) { 
						return TRUE ; 
					}
	}
	exec_command(cmd_info,v);
	return TRUE ;
}
	
char* search_cmd_dir_in_path(char* cmd,env_var_t * v) {
	FILE * fp ; 
	char * aux ; 
	char * path ; 
	int i = 0 ; 

	aux = (char*) malloc (sizeof(char)*MAX_STR) ;
	while( v->path[i] != NULL ) { 
		strcpy(aux,v->path[i]) ; 
		path = strcat(aux,"/") ; 
		path = strcat(aux,cmd) ; 
		fp = fopen(path,"r") ; 
		if ( fp == NULL ) { 
			if ( errno != ENOENT ){ 
				perror(strerror(errno)) ; 
			} 
			i++ ;
		} else { 
			fclose(fp) ; 
			return path ; 
		}
	}
	fprintf(stderr,"Comando no encontrado:%s\n",cmd) ;
	return NULL ; 
} 

void set_path(env_var_t * v){
	char * path;
	char * aux;
	int i = 0 ;
	path = getenv("PATH") ; 
	aux = strtok(path,":");

	v->path = (char**) malloc ( sizeof(char*) * BUFFER_SIZE ) ; 
	if (v->path==NULL){ 
		strerror(errno) ; 
		exit(EXIT_FAILURE) ; 
	}

	for ( ; i<BUFFER_SIZE ; i++ ){ 
		(v->path)[i] = NULL ; 
	}	

	i = 0 ;

	while(aux != NULL){
		(v->path)[i] = (char*) malloc (sizeof(char)* MAX_STR ) ;
		strcpy((v->path)[i],aux) ; 
		aux = strtok(NULL,":");
		i++;
	}

}

void init_env_var ( env_var_t * v ) { 
	FILE * fd;

	v->username = getenv("LOGNAME");

	v->prompt = (char *)malloc(MAX_STR * sizeof(char));
	if ( v->prompt == NULL ) { 
		perror("No se pudo reservar memoria.") ; 
		exit(EXIT_FAILURE);
	}
	v->hostname = (char *)malloc(MAX_STR * sizeof(char));	
	if ( v->hostname == NULL ) { 
		perror("No se pudo reservar memoria.") ; 
		exit(EXIT_FAILURE);
	}

	if((v->localpath = getcwd(NULL, 64)) == NULL){
		perror("No se pudo obtener el path");
		exit(EXIT_FAILURE);
	}

	fd = fopen("/etc/hostname", "r");
	if(fd == NULL){
		perror("Fallo al abrir el archivo");
		exit(EXIT_FAILURE);
	}
	
	fgets(v->hostname, MAX_STR, fd);	

	if(fclose(fd) == EOF){
		perror("No se pudo cerrar el archivo");
		exit(EXIT_FAILURE);
	}

	v->hostname[strlen(v->hostname)-1] = '\0';

	set_path(v); 
}

void get_prompt(env_var_t *v){

	sprintf(v->prompt, "%s@%s:%s>",v->username, v->hostname, v->localpath);

}

void change_dir(char * path,env_var_t * v){
	if(chdir(path) == -1){
		perror("");	
	}
	if((v->localpath = getcwd(NULL, 64)) == NULL){
			perror("No se pudo obtener el path");
			exit(EXIT_FAILURE);
	}
}


void destroy_env_var( env_var_t * v) {
	int i = 0 ; 
	free(v->hostname) ; 
	free(v->prompt) ; 
	for ( ; i < BUFFER_SIZE ; i++ ) { 
		free(v->path[i]) ; 
	} 
}
